﻿//-----------------------------------------------------------------------
// <copyright file="FilterStreamReader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation.
// All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// This code released under the terms of the 
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
// </copyright>
// <author>Yogesh M Joshi</author>
// <email>ymjoshi@live.com</email>
// <date>16-May-2011</date>
//-----------------------------------------------------------------------

namespace SmartTag.Common.IFilter
{
    using System;
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;
    using SmartTag.Common.Utility;

    /// <summary>
    /// This class wraps the functionality of stream reader for sharepoint document
    /// It interacts with the filter and extracts the text for specified document
    /// </summary>
    public class FilterStreamReader : TextReader
    {
        #region "Private Fields"

        /// <summary>
        /// Lenght of the read buffers
        /// </summary>
        private const uint BufferLength = 8192;

        /// <summary>
        /// Filter wrapper object
        /// </summary>
        private FilterWrapper filter;

        /// <summary>
        /// Status of the current chunk
        /// </summary>
        private STAT_CHUNK chunkStat;

        /// <summary>
        /// Flag to indicate if current chunk is valid
        /// </summary>
        private bool isCurrentChunkValid;

        /// <summary>
        /// Current read buffer for IFilter
        /// </summary>
        private char[] readBuffer = new char[BufferLength];

        /// <summary>
        /// Last remaining buffer
        /// </summary>
        private char[] lastLeftBuffer = new char[BufferLength];

        /// <summary>
        /// Last remaining buffer length
        /// </summary>
        private int lastLeftBufferLength = 0;

        #endregion

        /// <summary>
        /// Initializes a new instance of the FilterStreamReader class
        /// </summary>
        /// <param name="filter">Filter to read text from</param>
        public FilterStreamReader(FilterWrapper filter)
        {
            this.filter = filter;
        }

        /// <summary>
        /// Read the block of characters
        /// </summary>
        /// <param name="buffer">Read buffer</param>
        /// <param name="index">Index in buffer</param>
        /// <param name="count">Total length of boffer</param>
        /// <returns>Number of characters read</returns>
        public override int Read(char[] buffer, int index, int count)
        {
            int charsRead = 0;
            uint readBufferLength = BufferLength;
            bool lastChunk = false;

            try
            {
                // Check if there is any last left buffer
                if (this.lastLeftBufferLength != 0)
                {
                    // Last left buffer is enough to fill current buffer
                    if (this.lastLeftBufferLength > count)
                    {
                        int charsToCopy = count;
                        int charsRemaining = this.lastLeftBufferLength - count;

                        // Copy the characters
                        Array.Copy(this.lastLeftBuffer, buffer, charsToCopy);

                        // Copy characters in temp buffer
                        char[] tempBuffer = new char[charsRemaining];
                        Array.Copy(this.lastLeftBuffer, count, tempBuffer, 0, charsRemaining);

                        // Copy back to last left buffer
                        Array.Copy(tempBuffer, this.lastLeftBuffer, charsRemaining);
                        this.lastLeftBufferLength = charsRemaining;
                        return count;
                    }

                    // Copy last left buffer and continue
                    charsRead += this.lastLeftBufferLength;
                    Array.Copy(this.lastLeftBuffer, buffer, this.lastLeftBufferLength);
                    this.lastLeftBufferLength = 0;
                }

                // Read characters
                while (charsRead < count && lastChunk == false)
                {
                    if (this.isCurrentChunkValid == false)
                    {
                        IFilterReturnCode ret = this.filter.GetChunk(out this.chunkStat);

                        if (ret == IFilterReturnCode.S_OK
                            && (this.chunkStat.flags & CHUNKSTATE.CHUNK_TEXT) != 0)
                        {
                            this.isCurrentChunkValid = true;
                        }
                        else
                        {
                            if (ret == IFilterReturnCode.FILTER_E_END_OF_CHUNKS)
                            {
                                this.isCurrentChunkValid = true;
                                lastChunk = true;
                            }
                        }
                    }

                    if (this.isCurrentChunkValid == true)
                    {
                        readBufferLength = BufferLength;
                        IFilterReturnCode ret = this.filter.GetText(ref readBufferLength, this.readBuffer);

                        // Check if we got some text
                        if (ret == IFilterReturnCode.S_OK
                           || ret == IFilterReturnCode.FILTER_S_LAST_TEXT)
                        {
                            charsRead += (int)readBufferLength;

                            // Check if we are over the size of given buffer
                            if (charsRead < count == false)
                            {
                                // We have to copy remaining chars in last read buffer
                                int remainingCapacity = count - (charsRead - (int)readBufferLength);
                                int indexInBuffer = count - remainingCapacity;
                                this.lastLeftBufferLength = charsRead - count;

                                // Fill the buffer
                                Array.Copy(this.readBuffer, 0, buffer, indexInBuffer, remainingCapacity);

                                // Copy remaining characters in lastRemainingBuffer
                                Array.Copy(this.readBuffer, remainingCapacity, this.lastLeftBuffer, 0, readBufferLength - remainingCapacity);
                                this.lastLeftBufferLength = (int)readBufferLength - remainingCapacity;

                                // Set the chars read
                                charsRead = count;
                            }
                            else
                            {
                                Array.Copy(this.readBuffer, 0, buffer, charsRead - readBufferLength, readBufferLength);
                            }
                        }

                        // There is no more text in this chunk
                        if (ret == IFilterReturnCode.FILTER_S_LAST_TEXT
                            || ret == IFilterReturnCode.FILTER_E_NO_MORE_TEXT)
                        {
                            this.isCurrentChunkValid = false;
                        }

                        if (lastChunk)
                        {
                            this.isCurrentChunkValid = false;
                            break;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Error occured in ReadFilterStream", (int)TraceLogEventId.ReadFilterStream);
            }

            return (int)charsRead;
        }

        /// <summary>
        /// Reads given count of words from stream
        /// </summary>
        /// <param name="count">Approximate count</param>
        /// <returns>String of words</returns>
        public string ReadWords(int count)
        {
            try
            {
                int num;
                char[] buffer = new char[0x1000];
                StringBuilder builder = new StringBuilder(0x1000);

                while ((num = this.Read(buffer, 0, buffer.Length)) != 0
                    && WordCount(builder.ToString()) < count)
                {
                    builder.Append(buffer, 0, num);
                }

                return builder.ToString();
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Error occured in ReadWords", (int)TraceLogEventId.ReadWords);
                return null;
            }
        }

        /// <summary>
        /// Returns the word count in string
        /// </summary>
        /// <param name="str">String to count words from</param>
        /// <returns>Number of words</returns>
        private static int WordCount(string str)
        {
            int count = 0;
            string pattern = @"[^\W\d](\w|[-']{1,2}(?=\w))*";
            Regex regex = new Regex(pattern);
            count = regex.Matches(str).Count;
            return count;
        }
    }
}
